import java.util.List;

import java.util.ArrayList;

public class Grid
{
    private List<List<BlockObject>> BlockObjects = new ArrayList<List<BlockObject>>();
    private List<TrampolineRoute> routes = new ArrayList<TrampolineRoute>();
    private Robot currentRobot;
    private int width, height;
    private String StringGrid;
    private int Water, Floodrate; // w, dw/dt
    private int WaterCycle = 0;
    private int Growth;

    public List<List<BlockObject>> getBlockObjects()
    {
	return BlockObjects;
    }

    public List<TrampolineRoute> getRoutes()
    {
	return routes;
    }

    public void setRoutes(List<TrampolineRoute> routes)
    {
	this.routes = routes;
    }

    public int getWidth()
    {
	return width;
    }

    public int getHeight()
    {
	return height;
    }

    public Grid(String input)
    {
	StringGrid = input;
	// Get Width/Height
	width = getWidth(input);
	height = getHeight(input);
	StringGrid = filterInput(input);
	getMetadata(input);
	BlockObjects = setBlockObjects();
	getRobotMetadata(input);
    }

    public Grid(Grid g) // Copies grid properties over to current grid
    {
	width = g.width;
	height = g.height;
	StringGrid = g.StringGrid;
	Water = g.Water;
	Floodrate = g.Floodrate;
	WaterCycle = g.WaterCycle;
	Growth = g.Growth;
	BlockObjects = g.BlockObjects;
	currentRobot = g.currentRobot;
    }

    public Grid(String[] s)
    {
	String input = "";
	for (String row : s)
	    input += row + "\n";
	StringGrid = input;
	// Get Width/Height
	width = getWidth(input);
	height = getHeight(input);
	StringGrid = filterInput(input);
	getMetadata(input);
	BlockObjects = setBlockObjects();
	getRobotMetadata(input);
    }

    public int getWidth(String input) // Find largest width
    {
	int largestWidth = -1;
	String[] inputSplit = input.split("\n");
	for (int i = 0; i < inputSplit.length; i++)
	{
	    if (inputSplit[i].length() > largestWidth)
	    {
		largestWidth = inputSplit[i].length();
	    }
	}
	return largestWidth;
    }

    public void getRobotMetadata(String input)
    {
	for (String s : input.split("\n"))
	{
	    if (s.contains("Razors"))
	    {
		currentRobot.setRazors(Integer.parseInt(s.replace("Razors", "").replace(" ", "")));
	    }
	    if (s.contains("Waterproof"))
	    {
		currentRobot.setWaterProof(Integer.parseInt(s.replace("Waterproof", "").replace(" ", "")));
	    }
	    if (s.contains("Trampoline"))
	    {
		TrampolineRoute tr = new TrampolineRoute();
		tr.from = extractChar(s.replace("Trampoline", "").replace("targets", ""));
		tr.to = (char) extractInt(s.replace("Trampoline", "").replace("targets", "").replace(" ", ""));
		routes.add(tr);
	    }
	}
    }

    public void getMetadata(String input) // Get Data
    {
	for (String s : input.split("\n"))
	{
	    if (s.contains("Flooding"))
	    {
		Floodrate = Integer.parseInt(s.replace("Flooding", "").replace(" ", ""));
	    } else
	    {
		if (s.contains("Water"))
		{
		    Water = Integer.parseInt(s.replace("Water", "").replace(" ", ""));
		}
	    }
	    if (s.contains("Growth"))
	    {
		Growth = Integer.parseInt(s.replace("Growth", "").replace(" ", ""));
	    }
	}
    }

    public String filterInput(String input) // Strip metadata and Pad
    {
	String output = "";
	String[] inputSplit = input.split("\n");
	for (int i = 0; i < inputSplit.length; i++)
	{
	    String s = inputSplit[i];
	    if (s.contains("Water") || s.contains("Flooding") || s.contains("Trampoline") || s.contains("Razors") || s.contains("Growth")) // If
																	   // line
																	   // is
																	   // metadata,
																	   // ignore
	    {
		continue;
	    }
	    output += String.format("%1$-" + width + "s", s); // Pad the right
							      // with spaces
	}
	return output;
    }

    public int getHeight(String input) // Gets height of map by counting new
				       // lines
    {
	return input.split("\n").length;
    }

    public List<List<BlockObject>> setBlockObjects()
    {
	List<List<BlockObject>> returnBlocks = new ArrayList<List<BlockObject>>();

	for (int x = 0; x < width; x++)
	{
	    List<BlockObject> column = new ArrayList<BlockObject>();
	    for (int y = 0; y < height; y++)
	    {
		char Dest = getXY(x, y);
		if (Character.isLetter(Dest) && (int) Dest >= 65 && (int) Dest <= 73) // Is
										      // trampoline
		{
		    Trampoline tempTramp = new Trampoline(new Point(x, y), Dest);
		    tempTramp.setTarget(TrampolineRoute.getTrampolineTarget(this, getRoutes(), tempTramp));
		    tempTramp.setTargetInt(Integer.parseInt(tempTramp.getTarget().getCharacter() + ""));
		    column.add(tempTramp);
		}
		if (Character.isDigit(Dest) && Integer.parseInt("" + Dest) >= 0 && Integer.parseInt("" + Dest) <= 9)
		{ // Is trampoline target
		    column.add(new TrampolineTarget(new Point(x, y), Dest));
		}
		switch (Dest)
		{
		case '.':
		    column.add(new Soil(new Point(x, y), Dest));
		    break;
		case '*':
		    column.add(new Rock(new Point(x, y), Dest));
		    break;
		case 'L':
		    column.add(new Lift(new Point(x, y), Dest));
		    break;
		case '#':
		    column.add(new Wall(new Point(x, y), Dest));
		    break;
		case ' ':
		    column.add(new Air(new Point(x, y), Dest));
		    break;
		case 'R':
		    Robot r = new Robot(new Point(x, y), Dest);
		    currentRobot = r; // Set currentRobot to reference the robot
				      // on grid
		    column.add(r);
		    break;
		case 'W':
		    column.add(new Beard(new Point(x, y), Dest));
		    break;
		case '!':
		    column.add(new Razor(new Point(x, y), Dest));
		    break;
		case '\\':
		    column.add(new Lambda(new Point(x, y), Dest));
		    break;
		default: // Unforseen objects are just assumed air for now.
		    column.add(new Air(new Point(x, y), Dest));
		    break;
		}
	    }
	    returnBlocks.add(column);
	}
	return returnBlocks;
    }

    public char getXY(int x, int y)
    {
	int characters = y * width + x; // Height * width of each row + x =
					// index of character.
	if (characters >= StringGrid.length() || characters < 0)
	{
	    return ' '; // Return blank if invalid
	}
	return StringGrid.charAt(characters);
    }

    public Robot getCurrentRobot()
    {
	return currentRobot;
    }

    public void setCurrentRobot(Robot currentRobot)
    {
	this.currentRobot = currentRobot;
    }

    public int getWater()
    {
	return Water;
    }

    public void setWater(int water)
    {
	Water = water;
    }

    public int getFloodrate()
    {
	return Floodrate;
    }

    public void setFloodrate(int floodrate)
    {
	Floodrate = floodrate;
    }

    public int getWaterCycle()
    {
	return WaterCycle;
    }

    public void setWaterCycle(int waterCycle)
    {
	WaterCycle = waterCycle;
    }

    public void setWidth(int width)
    {
	this.width = width;
    }

    public int extractInt(String x)
    {
	for (int i = 0; i < x.length(); i++)
	{
	    if (Character.isDigit(x.charAt(i)))
	    {
		int j = Integer.parseInt(x, i);
		return j;
	    }
	}
	return -1; // No target
    }

    public char extractChar(String x)
    {
	for (int i = 0; i < x.length(); i++)
	{
	    if (Character.isLetter(x.charAt(i)))
	    {
		return x.charAt(i);
	    }
	}
	return '-'; // No char
    }

    // Validate move
    public BlockObject returnBlockFromPoint(Point p)
    {
	return BlockObjects.get(p.getX()).get(p.getY());
    }

    public boolean isAir(Point z)
    {
	return (returnBlockFromPoint(z).getCharacter() == ' ');
    }

    public boolean isRobotValidTarget(Point p, char dir) // Target to move from,
							 // Direction of
							 // movement
    {
	Point to = getDirectedPoint(p, dir);
	char TargetValue = BlockObjects.get(to.getX()).get(to.getY()).getCharacter();
	if (Character.isLetter(TargetValue) && (int) TargetValue >= 65 && (int) TargetValue <= 73) // Is
												   // trampoline
	{
	    return true;
	}
	if (Character.isDigit(TargetValue) && Integer.parseInt("" + TargetValue) >= 0 && Integer.parseInt("" + TargetValue) <= 9)
	{ // Is trampoline target
	    return false;
	}
	switch (TargetValue)
	{
	case ' ': // Air
	    return true;
	case '*': // Rock
	    Point k = getDirectedPoint(to, dir);
	    if (returnBlockFromPoint(k).getCharacter() == ' ') // If space is
							       // empty, valid
	    {
		return true;
	    }
	    return false;
	case '.': // Dirt
	    return true;
	case 'W': // Beard
	    return false;
	case '#': // Wall
	    return false;
	case '!': // Razor
	    return true;
	case '\\': // Lambda
	    return true;
	case 'L': // Lift
	    return true;
	}
	return false;
    }

    public Point getDirectedPoint(Point o, char k)
    {
	Point n = new Point(o.getX(), o.getY());
	if (k == 'U')
	    n = new Point(o.getX(), o.getY() - 1);
	if (k == 'D')
	    n = new Point(o.getX(), o.getY() + 1);
	if (k == 'L')
	    n = new Point(o.getX() - 1, o.getY());
	if (k == 'R')
	    n = new Point(o.getX() + 1, o.getY());
	return n;
    }

    // Move Object
    public void moveRobot(char k)
    {
	Point to = getDirectedPoint(currentRobot.getLocation(), k);
	if (isRobotValidTarget(currentRobot.getLocation(), k)) // Validate move
							       // is legal
	{
	    char dest = returnBlockFromPoint(to).getCharacter();
	    if (dest == '*') // If it is a rock, move the rock
	    {
		Point rockTarget = getDirectedPoint(to, k);
		teleport(to, rockTarget); // Teleport rock
	    }
	    if (Character.isLetter(dest) && (int) dest >= 65 && (int) dest <= 73) // Is
										  // trampoline
	    {
		to = ((Trampoline) BlockObjects.get(to.getX()).get(to.getY())).getTarget().getLocation();
	    }
	    teleport(currentRobot.getLocation(), to);
	}
    }

    public Point moveRock(Point from, Point to, Grid oldGrid)
    {
	// Check through old grid update on new grid
	if (oldGrid.BlockObjects.get(to.getX()).get(to.getY()).getCharacter() == ' ' && from.getY() <= to.getY())
	// If blank and is moving/falling to or below current level
	{
	    teleport(from, to); // Go teleport
	    // Check if it fell on the robot
	    if (BlockObjects.get(to.getX()).get(to.getY() + 1).getCharacter() == 'R')
	    {
		currentRobot.setDead(true);
	    }
	    return to;
	}
	return from;
    }

    public void teleport(Point from, Point to)
    { // Swap object with destination, change current location to air.
      // BlockObjects.get[to.X][to.Y] = BlockObjects[from.X][from.Y]; //Set
      // Destination Object to point to from Object
	BlockObjects.get(to.getX()).set(to.getY(), BlockObjects.get(from.getX()).get(from.getY()));
	BlockObjects.get(from.getX()).set(from.getY(), new Air(from, ' '));
	// BlockObjects[from.X][from.Y] = new Air(from, ' '); //Set from object
	// to point to a new Air object
    }

    public String toString()
    {
	String output = "";
	for (int y = 0; y < height; y++)
	{
	    for (int x = 0; x < width; x++)
	    {
		output += BlockObjects.get(x).get(y).getCharacter();
	    }
	    output += '\n';
	}
	return output;
    }

    public List<BlockObject> flattenBlockObjects() // Returns 1D List of
						   // BlockObjects in the
						   // BlockObject variable
    {
	List<BlockObject> bos = new ArrayList<BlockObject>();
	for (int y = 0; y < height; y++)
	{
	    for (int x = 0; x < width; x++)
	    {
		bos.add(BlockObjects.get(x).get(y));
	    }
	}
	return bos;
    }
}
